package work.linruchang.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.LineIter;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.stream.StreamUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 增强的文件工具类
 *
 * @author LinRuChang
 * @version 1.0
 * @date 2022/09/27
 * @since 1.8
 **/
@Slf4j
public class EnhanceFileUtil extends FileUtil {
    private static DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();

    /**
     * java文件的代码行数
     * @param filePath 文件或者目录
     * @return
     */
    public static Long javaFileRowCount(String filePath) {
        return javaFileRowCount(FileUtil.file(filePath));
    }

    /**
     * java文件的代码行数
     * @param file 文件或者目录
     * @return
     */
    public static Long javaFileRowCount(File file) {
        AtomicLong resultRowCount = new AtomicLong();
        if(!FileUtil.exist(file)) {
            return resultRowCount.get();
        }

        //文件或者目录内的所有文件
        List<File> javaFiles = CollUtil.newArrayList();
        if(FileUtil.isFile(file)) {
            String extName = FileUtil.extName(file);
            if(StrUtil.equalsAnyIgnoreCase(extName, "java")) {
                javaFiles.add(file);
            }
        }else {
            javaFiles = FileUtil.loopFiles(file, currentFile -> {
                String extName = FileUtil.extName(currentFile);
                return StrUtil.equalsAnyIgnoreCase(extName, "java");
            });
        }

        //开始计算每个文件的代码总数
        javaFiles.stream()
                .parallel()
                .forEach(javaFile -> {

                    Integer currentFileRowCount = FileUtil.readLines(javaFile, StandardCharsets.UTF_8)
                            .stream()
                            .filter(StrUtil::isNotBlank)
                            .map(StrUtil::trim)
                            //去注释、去包名、去引入依赖、去空行后的行信息
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"package") && !StrUtil.startWithIgnoreCase(currentRow,"import"))
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"//"))
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"/*"))
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"*"))
                            .map(currentRow -> 1)
                            .reduce(Integer::sum)
                            .orElse(0);

                    resultRowCount.addAndGet(Convert.toLong(currentFileRowCount));
                });

        return resultRowCount.get();
    }

    /**
     * java文件的代码量（字符数）
     * @param filePath 文件或者目录
     * @return
     */
    public static Long javaFileCodeCount(String filePath) {
        return javaFileCodeCount(FileUtil.file(filePath));
    }

    /**
     * java文件的代码量（字符数）
     * @param file 文件或者目录
     * @return
     */
    public static Long javaFileCodeCount(File file) {
        AtomicLong resultCodeCount = new AtomicLong();
        if(!FileUtil.exist(file)) {
            return resultCodeCount.get();
        }

        //文件或者目录内的所有文件
        List<File> javaFiles = CollUtil.newArrayList();
        if(FileUtil.isFile(file)) {
            String extName = FileUtil.extName(file);
            if(StrUtil.equalsAnyIgnoreCase(extName, "java")) {
                javaFiles.add(file);
            }
        }else {
            javaFiles = FileUtil.loopFiles(file, currentFile -> {
                String extName = FileUtil.extName(currentFile);
                return StrUtil.equalsAnyIgnoreCase(extName, "java");
            });
        }

        //开始计算每个文件的代码总数
        javaFiles.stream()
                .parallel()
                .forEach(javaFile -> {

                    Integer currentFileCodeCount = FileUtil.readLines(javaFile, StandardCharsets.UTF_8)
                            .stream()
                            .filter(StrUtil::isNotBlank)
                            .map(StrUtil::trim)
                            //去注释、去包名、去引入依赖、去空行后的行信息
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"package") && !StrUtil.startWithIgnoreCase(currentRow,"import"))
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"//"))
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"/*"))
                            .filter(currentRow -> !StrUtil.startWithIgnoreCase(currentRow,"*"))
                            .map(StrUtil::length)
                            .reduce(Integer::sum)
                            .orElse(0);

                    resultCodeCount.addAndGet(Convert.toLong(currentFileCodeCount));
                });

        return resultCodeCount.get();
    }

    /**
     * 获取新的随机文件名
     * @param sourceFileName 源文件名
     * @return 格式：uuid.源文件类型
     */
    public static String newRandomFileName(String sourceFileName) {
        return StrUtil.join(StrUtil.DOT, UUID.randomUUID().toString(true), extName(sourceFileName));
    }

    /**
     * 文本文件的总行数
     *  注意：大文本文件统计行数非常非常的耗时，不建议使用（一个文件30G就非常不建议使用了）
     * @param sourceTextFile 文本文件
     * @return
     */
    public static Long textFileTotalRow(File sourceTextFile) {
        boolean fileExistFlag = sourceTextFile != null && FileUtil.exists(sourceTextFile.toPath(), true);
        if (fileExistFlag) {
            return StreamUtil.of(sourceTextFile)
                    .count();
        }
        return 0L;
    }

    /**
     * 分割文本文件（多线程）- 不太大文件使用
     * 注意：需要先读取文件的总行数（所以文件很大很大不建议使用该方法，例如一个文件30G非常不建议使用）
     * @param sourceTextFile   源文件
     * @param singleFileRowNum 分割后每个文件的最大行数
     * @return 分割的文件
     */
    @SneakyThrows
    public static List<File> textFileCutting(File sourceTextFile, Long singleFileRowNum) {
        Long totalRow = textFileTotalRow(sourceTextFile);
        log.info("文件【{}】总行数：{}", FileUtil.getName(sourceTextFile), totalRow);

        // 文件读取行数
        CountDownLatch rowCountDownLatch = new CountDownLatch(Convert.toInt(totalRow));

        List<File> cuttingFiles = Collections.synchronizedList(CollUtil.newArrayList());
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) ThreadUtil.newExecutor(32);
        executorService.setKeepAliveTime(1, TimeUnit.SECONDS);
        executorService.allowCoreThreadTimeOut(true);
        if (totalRow > 0) {
            final File currentSourceTextFileDir = FileUtil.getParent(sourceTextFile, 1);

            // 切割的文件数
            Long cuttingFileCount = totalRow % singleFileRowNum > 0 ? totalRow / singleFileRowNum + 1 : totalRow % singleFileRowNum;
            log.info("文件【{}】切割信息：总行数【{}】、每个切割文件的最大行数【{}】、共切割成【{}】个文件", FileUtil.getName(sourceTextFile), totalRow, singleFileRowNum, cuttingFileCount);

            CountDownLatch cuttingFileCountDownLatch = new CountDownLatch(Convert.toInt(cuttingFileCount));

            for (int cutFileIndex = 0; cutFileIndex < cuttingFileCount; cutFileIndex++) {
                String cutFileIndexStr = StrUtil.padPre(Convert.toStr(cutFileIndex), 5, "0");
                executorService.execute(() -> {
                    File cutFile = FileUtil.file(currentSourceTextFileDir, StrUtil.format("{}_cut{}.{}",FileUtil.mainName(sourceTextFile), cutFileIndexStr, StrUtil.blankToDefault(FileUtil.extName(sourceTextFile),"txt")));

                    //当前分割文件从源文件读取的开始行、结束行
                    Long startRow = (Convert.toInt(cutFileIndexStr) * singleFileRowNum) + 1;
                    Long endRow = ((Convert.toInt(cutFileIndexStr)+ 1)  * singleFileRowNum) ;

                    AtomicLong cuurentRow = new AtomicLong();

                    LineIter lineIter = EnhanceIoUtil.lineIter(sourceTextFile);
                    while (lineIter.hasNext() && cuurentRow.incrementAndGet() <= endRow) {
                        String lineContent = lineIter.next();
                        if(cuurentRow.get()>=startRow) {
                            FileUtil.writeLines(Arrays.asList(lineContent), cutFile, StandardCharsets.UTF_8,true);
                            rowCountDownLatch.countDown();
                        }
                    }

                    cuttingFiles.add(cutFile);
                    cuttingFileCountDownLatch.countDown();
                });
            }
            cuttingFileCountDownLatch.await();
        }
        rowCountDownLatch.await();
        return cuttingFiles;
    }

    /**
     * 分割文本文件（单线程）- 超大文件使用
     * @param sourceTextFile  源文件
     * @param singleFileRowNum 分割后每个文件的最大行数
     * @return 分割的文件
     */
    @SneakyThrows
    public static List<File> textFileCutting2(File sourceTextFile, Long singleFileRowNum) {
        Set<File> cuttingFiles = Collections.synchronizedSet(CollUtil.newHashSet());
        boolean fileFlag = FileUtil.isFile(sourceTextFile);
        AtomicLong row = new AtomicLong();
        if(fileFlag) {
            final File currentSourceTextFileDir = FileUtil.getParent(sourceTextFile, 1);
            StreamUtil.of(sourceTextFile)
                    .forEach(line -> {
                        long currentRow = row.incrementAndGet();
                        Long cutFileIndex = (currentRow%singleFileRowNum == 0) ? currentRow/singleFileRowNum-1:currentRow/singleFileRowNum;
                        String cutFileIndexStr = StrUtil.padPre(Convert.toStr(cutFileIndex), 5, "0");
                        File cutFile = FileUtil.file(currentSourceTextFileDir, StrUtil.format("{}_cut{}.{}",FileUtil.mainName(sourceTextFile), cutFileIndexStr, StrUtil.blankToDefault(FileUtil.extName(sourceTextFile),"txt")));
                        FileUtil.writeLines(Arrays.asList(line), cutFile, StandardCharsets.UTF_8,true);
                        cuttingFiles.add(cutFile);
                    });
        }

        log.info("文件【{}】切割信息：总行数【{}】、每个切割文件的最大行数【{}】、共切割成【{}】个文件", FileUtil.getName(sourceTextFile), row, singleFileRowNum, cuttingFiles.size());
        return CollUtil.newArrayList(cuttingFiles);
    }

    /**
     * 读取Spring文件地址形式的文件
     * 仅取第一个存在的有效文件
     * @param springFilePaths Spring格式的Path路径 参考{@link ResourceUtils#CLASSPATH_URL_PREFIX}
     * @return
     */
    @SneakyThrows
    public static String readSpringFileContent(List<String> springFilePaths) {

        Resource resultResource = CollUtil.emptyIfNull(springFilePaths)
                .stream()
                .filter(StrUtil::isNotBlank)
                .map(defaultResourceLoader::getResource)
                .filter(Resource::exists)
                .findFirst()
                .orElse(null);

        //含有生效的文件
        if(resultResource != null) {
            InputStream inputStream = resultResource.getInputStream();
            String inputStreamContent = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8);
            return inputStreamContent;
        }

        //无生效的文件
        return null;
    }



    public static void main(String[] args) {

        Long l = javaFileCodeCount(FileUtil.file("G:\\IDEA2021.3\\workspace\\lrc\\lrctool\\src"));
        Console.log(l);

        l = javaFileCodeCount(FileUtil.file("G:\\IDEA2021.3\\workspace\\lrc\\lrctool\\src\\main\\java\\work\\linruchang\\util\\MyMockUtil.java"));
        Console.log(l);


        l = javaFileRowCount(FileUtil.file("G:\\IDEA2021.3\\workspace\\lrc\\lrctool\\src"));
        Console.log(l);

        l = javaFileRowCount(FileUtil.file("G:\\IDEA2021.3\\workspace\\lrc\\lrctool\\src\\main\\java\\work\\linruchang\\util\\MyMockUtil.java"));
        Console.log(l);
    }
}
